package org.flexthinker.binding.bean
{
    import mx.events.PropertyChangeEvent;
    import mx.logging.ILogger;
    import mx.logging.Log;

    /**
     * An implementation of <code>Binding</code> that automatically syncs the source
     * and target by refreshing and saving according to one of the three update strategies.
     * The update strategy is specified for an <code>AutoBinding</code> on creation, and is one of:
     * <p>
     * <ul>
     * <li><code>UpdateStrategy.READ_ONCE</code></li>
     * <li><code>UpdateStrategy.READ</code></li>
     * <li><code>UpdateStrategy.READ_WRITE</code></li>
     * </ul>
     * </p>
     *
     *
     * <table border="0">
     * <tr valign="baseline">
     * <td><b><font size="+1"><code>READ_ONCE</code></font></b></td>
     * <td>&nbsp;&nbsp;&nbsp;</td>
     * <td> <b>Summary:</b><br />
     * Tries to sync the target from the source only once, at bind time.
     *
     * <b>Details:</b><br />
     * At bind time, tries to sync the target from the source, by calling
     * <code>refreshAndNotify</code>. No further automatic syncing is done. </td>
     * </tr>
     * <tr>
     * <td colspan="3"><br />
     * </td>
     * </tr>
     * <tr valign="baseline">
     * <td><b><font size="+1"><code>READ</code></font></b></td>
     * <td></td>
     * <td> <b>Summary:</b><br />
     * Tries to keep the target in sync with the source.
     *
     * <b>Details:</b><br />
     * At bind time, tries to sync the target from the source, by calling
     * <code>refreshAndNotify</code>. Then automatically tries to sync the target from
     * the source by calling <code>refreshAndNotify</code> when the source changes value. </td>
     * </tr>
     * <tr>
     * <td colspan="3"><br />
     * </td>
     * </tr>
     * <tr valign="baseline">
     * <td><b><font size="+1"><code>READ_WRITE</code></font></b></td>
     * <td>&nbsp;&nbsp;&nbsp;</td>
     * <td> <b>Summary:</b><br />
     * Tries to keep both the source and target in sync with each other.
     *
     * <b>Details:</b><br />
     * At bind time, first tries to sync the target from the source, by calling
     * <code>refreshAndNotify</code>.
     * <br />
     * If target is unwritable, a <code>SyncFailure</code> of type
     * <code>SyncFailureType.TARGET_UNWRITABLE</code> is sent to all binding listeners.
     * No further sync is being done initially.
     * <br />
     * If source is unreadable, a <code>SyncFailure</code> of type
     * <code>SyncFailureType.SOURCE_UNREADABLE</code> is sent to all binding listeners.
     * No further sync is being done initially.
      </td>
     * </tr>
     * </table>
     *
     *
     * @see org.flexthinker.binding.bean.UpdateStrategy
     * @see org.flexthinker.binding.bean.Binding
     * @see org.flexthinker.binding.bean.BindingListener
     */
    public class AutoBinding extends Binding
    {
        private var log:ILogger = Log.getLogger("org.flexthinker.binding.bean.AutoBinding");

        private var _updateStrategy:UpdateStrategy;

        /**
         * Creates a <code>Binding</code> between two properties of two objects
         * with the given update strategy.
         *
         * @param updateStrategy
         *              Defines when to sync the properties.
         * @param sourceObject
         *              The source object to be bound
         * @param sourceProperty
         *              The property to bound. Must be sourceObject's property.
         * @param targetObject
         *              The target object to bind to
         * @param targetProperty
         *              The property to bind to. Must be targetObject's property.
         * @param name
         *              Optional name for the Binding.
         */
        public function AutoBinding(updateStrategy:UpdateStrategy, sourceObject:Object, sourceProperty:PropertyHelper,
                targetObject:Object, targetProperty:PropertyHelper, name:String = null)
        {
            super(sourceObject, sourceProperty, targetObject, targetProperty, name);
            throwIfNullStrategy(updateStrategy);
            _updateStrategy = updateStrategy;
        }

        public function get updateStrategy():UpdateStrategy
        {
            return _updateStrategy;
        }

        // -------------------
        //   internal methods
        // -------------------
        private function throwIfNullStrategy(strategy:UpdateStrategy):void
        {
            if (strategy == null)
            {
                throw new ArgumentError("Please provide an update strategy");
            }
        }


        override protected function bindImpl():void
        {
            super.bindImpl();
            /*
             * Initially sync automatically the target with the source
             * regardless of the update strategies.
             */
            refreshAndNotify();
        }


        override protected function unbindImpl():void
        {
            super.unbindImpl();
        }


        override protected function sourceChangedImpl(event:PropertyChangeEvent):void
        {
            super.sourceChangedImpl(event);
            switch (_updateStrategy)
            {
            case UpdateStrategy.READ_ONCE:
                // nothing to do
                break;
            case UpdateStrategy.READ:
            case UpdateStrategy.READ_WRITE:
                refreshAndNotify(); // update target value with source's value
                break;
            }
        }


        override protected function targetChangedImpl(event:PropertyChangeEvent):void
        {
            super.targetChangedImpl(event);
            switch (_updateStrategy)
            {
            case UpdateStrategy.READ_ONCE:
            case UpdateStrategy.READ:
                // nothing to do
                break;
            case UpdateStrategy.READ_WRITE:
                saveAndNotify(); // update source value with target's value
                break;
            }
        }
    }
}
